#light
namespace FSharp.WebTools
#nowarn "57"

open System
open System.Collections.Generic
open System.Reflection
open System.Web.UI
open System.CodeDom.Compiler

open Microsoft.FSharp.Reflection
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Raw

open FSharp.WebTools
open FSharp.WebTools.Core
open FSharp.WebTools.Quotations
open FSharp.WebTools.Extensions

(*-----------------------------------------------------------------------------------------------*)
module ClientExpressions = 
(*-----------------------------------------------------------------------------------------------*)

  let urlEncode s = 
    if (System.Web.HttpContext.Current <> null) then
      System.Web.HttpContext.Current.Server.UrlEncode s
    else
      System.Web.HttpUtility.UrlEncode s

  // Utilities used for matching F# monadic syntax and for translating these to JS monads

  let optionTy = (type obj option).GetGenericTypeDefinition()
  let tupTyC2 = (type int*int).GetGenericTypeDefinition();
  let mkTupTy2 t1 t2 = tupTyC2.MakeGenericType([| t1; t2 |]);
  let tupTyC3 = (type int*int*int).GetGenericTypeDefinition();
  let mkTupTy3 t1 t2 t3 = tupTyC3.MakeGenericType([| t1; t2; t3 |]);

  let clientAsyncMonadTy = (type ClientAsyncMonad<obj>).GetGenericTypeDefinition();
  
  // Extracts topDefnUse from quoted member (needs to deconstruct lambda), because 
  // quoted members are wrapped like this  
  let rec getMemberAppOp' e = 
    match e with 
    | Lambda(_, e) | Let(_, e) | App(e, _) -> getMemberAppOp' e
    | AnyTopDefnUse _ -> Some e
    | MethodCall(mi, _) -> failwithf "Method call found in 'getMemberAppOp': %A" mi
    | _ -> failwithf "%A" e
  let getMemberAppOp e =
    match (getMemberAppOp' e) with
    | Some v -> v
    | None -> failwithf "Failed to extract AppOp: %A" e

  //
  let rec methodOf' e = 
    match e with 
    | Lambda(_, e) | Let(_, e) | App(e, _) -> methodOf' e
    | MethodCall(mi, _) -> Some(mi.GetGenericDefinitionSafe())
    | _ -> None
  let methodOf e =
    match (methodOf' e) with
    | Some v -> v
    | None -> failwithf "Failed to extract MethodInfo: %A" e
  
  let unitExpr = <@@ () @@>
  let clientUnitExpr = <@@ client_unit @@>
  let serverExecExpr = <@@ Core.serverExecute @@>
  let trackedAssignmentExpr = <@@ (<@! !@>) @@>  

  // matching against...  
  // async monad  
  let asyncDelay   = methodOf <@@ client_async.Delay @@> 
  let asyncRes     = methodOf <@@ client_async.Return @@> 
  let asyncLet     = methodOf <@@ client_async.Let @@>
  let asyncBind    = methodOf <@@ client_async.Bind @@> 
  let asyncZero    = methodOf <@@ client_async.Zero @@> 

  // client & duplex monad binders (translated to straightforward JS code)
  let clientDelay   = methodOf <@@ client.Delay @@> 
  let clientRes     = methodOf <@@ client.Return @@> 
  let clientLet     = methodOf <@@ client.Let @@>
  let clientBind    = methodOf <@@ client.Bind @@> 
  let clientZero    = methodOf <@@ client.Zero @@> 
  
  // Generic constraints cannot be resolved to default 
  // type variables, so we need some type annotations 
  let duplexDelay   = methodOf <@@ duplex.Delay : (unit -> DuplexMonad<obj>) -> DuplexMonad<obj> @@> 
  let duplexRes     = methodOf <@@ duplex.Return @@> 
  let duplexLet     = methodOf <@@ duplex.Let   : obj * (obj -> DuplexMonad<obj>) -> DuplexMonad<obj> @@>
  let duplexBind    = methodOf <@@ duplex.Bind  : DuplexMonad<obj> * (obj -> DuplexMonad<obj>) -> DuplexMonad<obj> @@>
  let duplexZero    = methodOf <@@ duplex.Zero @@>
    
  let makeDuplexExpr   = <@@ makeDuplex @@>  
  
  // constructing JS monad    
  let jsAsyncRes        = methodOf <@@ ClientLibrary.AsyncMonad.Result @@>
  let jsAsyncRun        = methodOf <@@ ClientLibrary.AsyncMonad.Run @@>
  let jsasyncBind       = methodOf <@@ ClientLibrary.AsyncMonad.Bind @@>
  let jsasyncLet        = methodOf <@@ ClientLibrary.AsyncMonad.Let @@>
  let jsAsyncZero       = methodOf <@@ ClientLibrary.AsyncMonad.Zero @@>
  
  let jsAsyncHttpCall   = methodOf <@@ ClientLibrary.Async.HttpCall @@>
  
  // Utilities used by active patterns below to do some additional handling
  
  let toExtExpr = List.map MkStdExpr
  let detupleArgs (args:Expr list) = 
    match args with 
    | [Tuple(_, a)] -> a 
    | [a; Tuple(_, b)] -> (a::b) 
    | _ -> args
    
  let detupleArgsEx (args:ExtendedExpr list) = 
    match args with 
    | [StdExpr(Tuple(_, a))] -> (a |> toExtExpr) 
    | [a; StdExpr(Tuple(_, b))] -> a::(b |> toExtExpr)  // TODO: this is needed only when mapping member method to static
    | _ -> args
    
  let rec removeLastArgWhenUnit unitExpr args =
    match args with 
    | v::[] when v = unitExpr -> [];
    | v::rest -> v::(removeLastArgWhenUnit unitExpr rest)
    | [] -> []
    
  let removeUnitArgs = removeLastArgWhenUnit unitExpr
  let removeUnitArgsEx = removeLastArgWhenUnit (MkStdExpr unitExpr)

  let expr_to_string = function
    | StdExpr e -> any_to_string e
    | ExtExpr e -> any_to_string e
    | _ -> assert(false)
    
  let isPropStatic (prop:PropertyInfo) = 
    let m = if (prop.CanRead) then prop.GetGetMethod() else prop.GetSetMethod()
    m.IsStatic      

  let getClassName (t:Type) = // todo namespaces...
    let n = t.Name
    let i = n.IndexOf('`');
    if (i = -1) then n else n.Substring(0, i)

  let clientType = (type IClient<obj>).GetGenericTypeDefinition()
  let clientAsyncType = (type IClientAsync<obj>).GetGenericTypeDefinition()
  let serverType = (type IServer<obj>).GetGenericTypeDefinition();

  let isServerType (t:System.Type) =
    t.GetInterfaces() |> Array.exists (fun ifc ->
      (ifc.IsGenericType) && (ifc.GetGenericTypeDefinition() = serverType) )
    
  let isClientType (t:System.Type) =
    t.GetInterfaces() |> Array.exists (fun ifc ->
      (ifc.IsGenericType) && ((ifc.GetGenericTypeDefinition() = clientType) || (ifc.GetGenericTypeDefinition() = clientAsyncType)) )

  let rec fillHoles (l:Expr list) e =   
    match e with 
    | BindingStructure.Var _ | BindingStructure.Quote _ -> e,l
    | BindingStructure.Lambda (v,b) -> 
        let b2,l = fillHoles l b in MkLambda(v, b2),l
    | RecdSet(t,s,e1,e2) -> // TODO: unchecked - recordset on class - omit this in the future
        let e1h,l = fillHoles l e1 
        let e2h,l = fillHoles l e2
        Unchecked.MkRecdSet(t,s,e1h,e2h), l
    | BindingStructure.ConstApp (op,args) ->
        let nl, args2 = args |> List.fold_left (fun (l, nargs) arg -> 
           let (arg2, l2) = fillHoles l arg in (l2, arg2::nargs)) (l, [])
        BindingStructure.MkConstApp(op, args2 |> List.rev),nl  
    | BindingStructure.Hole ty -> 
        match l with 
        | h :: t -> 
           if (h.Type <> ty) then failwith "fillHoles: type of the argument doesn't match the hole type"
           h,t 
        | [] -> failwith "fillHoles: not enough arguments!"

  // Creates an expression that represents call to a generic method
  // Unchecked - when mapping F# library calls to wrappers, the type may not match
  let MkGenericMethodUse (mi:MethodInfo) (tya, args) =
    Unchecked.MkMethodCall(mi.MakeGenericMethodSafe(tya), args)
    
  // Tests whether expression is use of specified MethodInfo and if yes
  // returns generic type arguments and arguments to the call
  let (|GenericMethodUse|_|) (mi:MethodInfo) e = 
    match e with
    | MethodCall(micall, args) 
      when micall.GetGenericDefinitionSafe() = mi.GetGenericDefinitionSafe() ->
        Some(mi.GetGenericArgumentsSafe(), args)
    | _ -> None

(*-----------------------------------------------------------------------------------------------*)
// External mappings 

  type ExternalMappingProcessor() =
    
    // Array.zero_create needs special handling, because we need to serialize the type
    let zeroCreate = <@@ Array.zero_create @@>
    let jsZeroCreate = methodOf <@@ ClientLibrary.Internal.ArrayModule.ZeroCreate @@>;

    // explicit mappings from top def functions (this is not a good way..)
    let explicitMappings  = 
      let m = 
        [(<@@ Core.asyncExecute @@>,  <@@ ClientLibrary.AsyncMonad.Run @@>);
         (<@@ ignore @@>,             <@@ ClientLibrary.Internal.ClientCore.Ignore @@>); 
         (<@@ float @@>,              <@@ ClientLibrary.Internal.ConvertModule.ToFloat @@>);
         (<@@ int @@>,                <@@ ClientLibrary.Internal.ConvertModule.ToInt @@>);
         (<@@ not @@>,                <@@ ClientLibrary.Internal.ClientCore.Not @@>); 
         (<@@ failwith @@>,           <@@ ClientLibrary.Internal.ClientCore.FailWith @@>); 
         (<@@ (@) @@>,                <@@ ClientLibrary.Internal.ListModule.Append @@>);
         (<@@ List.map @@>,           <@@ ClientLibrary.Internal.ListModule.Map @@>);
         (<@@ List.iter @@>,          <@@ ClientLibrary.Internal.ListModule.Iter @@>);
         (<@@ List.filter @@>,        <@@ ClientLibrary.Internal.ListModule.Filter @@>);
         (<@@ List.fold_left @@>,     <@@ ClientLibrary.Internal.ListModule.FoldLeft @@>);
         (<@@ List.length @@>,        <@@ ClientLibrary.Internal.ListModule.Length @@>);
         (<@@ Array.map @@>,          <@@ ClientLibrary.Internal.ArrayModule.Map @@>);
         (<@@ Array.filter @@>,       <@@ ClientLibrary.Internal.ArrayModule.Filter @@>);
         (<@@ Array.fold_left @@>,    <@@ ClientLibrary.Internal.ArrayModule.FoldLeft @@>);
         (<@@ Array.iter @@>,         <@@ ClientLibrary.Internal.ArrayModule.Iter @@>);
         (<@@ Array.iteri @@>,        <@@ ClientLibrary.Internal.ArrayModule.IterI @@>);
         (<@@ Array.exists @@>,       <@@ ClientLibrary.Internal.ArrayModule.Exists @@>);
         (<@@ Char.code @@>,          <@@ ClientLibrary.Internal.CharModule.Code @@>); 
         (<@@ Char.chr @@>,           <@@ ClientLibrary.Internal.CharModule.Chr @@>);
         (<@@ sprintf @@>,            <@@ ClientLibrary.Internal.PrintModule.Sprintf @@>);
         (<@@ (.[]<-) : obj[] -> int -> obj -> unit @@>,  <@@ ClientLibrary.Internal.Operators.IndexedAssign @@>);
         (<@@ (.[]) : obj[] -> int -> obj @@>,            <@@ ClientLibrary.Internal.Operators.IndexedLookup @@>); ]
      m |> List.map (fun (f,j) -> f, (methodOf j) )
    
    // 
    let externalTypesFrom = new Dictionary<_,_>()
    let externalTypesTo   = new Dictionary<_,_>()

    let registerAssembly(asm:Assembly) =
      asm.GetTypes() |> Array.iter (fun ty ->
        let attrs = ty.GetCustomAttributes(typeof<ExternalTypeAttribute>, false)
        if (attrs.Length > 0) then
          let mty = ((attrs.[0]) :?> ExternalTypeAttribute).Type
          let mty = if (mty.IsGenericType) then mty.GetGenericTypeDefinition() else mty        
          let ty   = if (ty.IsGenericType) then ty.GetGenericTypeDefinition() else ty
          externalTypesFrom.Add(mty, ty);
          externalTypesTo.Add(ty, mty); )
    
    do  registerAssembly(Assembly.GetExecutingAssembly())
    
    member x.TransformArrayCreate(e) =
      match e with
      | StdExpr(Raw.GenericTopDefnApp zeroCreate ([ty], args)) ->
          let encTy = MkString(Utils.encode (ty.MakeArrayType().AssemblyQualifiedName))
          Some(MkGenericMethodUse jsZeroCreate ([ty], args@[encTy]))
      | _ -> None    
    member x.LookupSystemType(ty) = 
      let b, v = externalTypesFrom.TryGetValue(ty)
      if (b) then Some(v) else None
    member x.LookupClientType(ty) = 
      let b, v = externalTypesTo.TryGetValue(ty)
      if (b) then Some(v) else None
    member x.ExplicitMappings = explicitMappings
    member x.RegisterAssembly(asm) = registerAssembly(asm)
  
  let mappingProc = ExternalMappingProcessor()

  let (|ClientExplicitlyMapped|_|) e =
    let semi = 
      mappingProc.ExplicitMappings |> List.first (fun (fs,js) -> 
        match e with 
        | StdExpr(Raw.GenericTopDefnApp fs (tya, args)) -> 
            Some (Unchecked.MkMethodCall(js.MakeGenericMethodSafe(tya), args))
        | _ -> None) 
    match semi with 
    | None -> mappingProc.TransformArrayCreate(e)
    | _ -> semi
    
  let (|MapDeclaringType|_|) (mi:#MemberInfo) =
    let ty = Utils.getGenericTypeDef mi.DeclaringType
    let args = Utils.getGenericArgs mi.DeclaringType
    mappingProc.LookupSystemType(ty) |> Option.bind (fun v ->
      Some(Utils.makeGenericTypeDef v args, mi) )
 
  let (|CastToMemberInfo|) (mi:#MemberInfo) = 
    (mi :> MemberInfo)
    
  let (|ClientExternalTypeUse|_|) e =
    try
      match e with
      | StdExpr(Fix.MethodCall(MapDeclaringType(mapTy, mi), args)) -> 
          let na = mi.GetParameters().Length
          let meths = mapTy.GetMethods() |> Array.filter (fun m ->
            (m.Name = mi.Name) && (m.GetParameters().Length = na))
          if (meths.Length <> 1) then failwith "!"
          Some(Unchecked.MkMethodCall(meths.[0], args))
      | StdExpr(Fix.PropGet(MapDeclaringType(mapTy, mi), args)) -> 
          let nmi = mapTy.GetProperty(mi.Name) 
          match args with 
            | [a] -> Some(Unchecked.MkPropGet(nmi, a))
            | _ -> Some(Unchecked.MkMethodCall(nmi.GetGetMethod(), args))
      | StdExpr(Fix.PropSet(MapDeclaringType(mapTy, mi), args)) -> 
          let nmi = mapTy.GetProperty(mi.Name) in Some(Unchecked.MkMethodCall(nmi.GetSetMethod(), args))
      | StdExpr(Fix.CtorCall(MapDeclaringType(mapTy, mi), args)) -> 
          let na = mi.GetParameters().Length
          let cts = mapTy.GetConstructors() |> Array.filter (fun ct -> 
            ct.GetParameters().Length = na)
          if (cts.Length > 1) then failwith "!"
          Some(Unchecked.MkCtorCall(cts.[0], args))
      | _ -> None
    with _ ->
      match e with
        | StdExpr(Fix.MethodCall(MapDeclaringType(mapTy, CastToMemberInfo(mi)), _))  
        | StdExpr(Fix.PropGet(MapDeclaringType(mapTy, CastToMemberInfo(mi)), _)) 
        | StdExpr(Fix.PropSet(MapDeclaringType(mapTy, CastToMemberInfo(mi)), _))  
        | StdExpr(Fix.CtorCall(MapDeclaringType(mapTy, CastToMemberInfo(mi)), _)) ->
            if (mi.Name = ".ctor" && mapTy.Name = "Object") then
              None // workaround - base constructor calls (there is no good way for catching them... :-( ..)
            else
              failwithf "Failed when mapping member '%s' to type '%s'." mi.Name mapTy.Name
        | _ -> failwith ("ClientExternalTypeUse failed! ")
          
(*-----------------------------------------------------------------------------------------------*)
// Active patterns used for translating ExtendedExpr to JavaScript - some of them are just
// combinations of StdExpr and some F# Expr value, some of them include additional handling
  
  /// Active pattern combinator
  let (>>?) g f v = match (g v) with | Some n -> (f n) | _ -> None
  
  // Some basic combinations
  let (|ClientCond|_|)      = (|StdExpr|_|) >>? Raw.(|Cond|_|)
  let (|ClientEquality|_|)  = (|StdExpr|_|) >>? Raw.(|Equality|_|)
  let (|ClientCoerce|_|)    = (|StdExpr|_|) >>? Raw.(|Coerce|_|)
  let (|ClientNewArray|_|)  = (|StdExpr|_|) >>? Raw.(|NewArray|_|)
  
  let (|ClientUnionMake|_|) = (|StdExpr|_|) >>? Raw.(|Sum|_|)
  let (|ClientUnionTest|_|) = (|StdExpr|_|) >>? Raw.(|SumTagTest|_|)
  let (|ClientUnionGet|_|)  = (|StdExpr|_|) >>? Raw.(|SumFieldGet|_|)
  
  let (|ClientTupleMake|_|) = (|StdExpr|_|) >>? Raw.(|Tuple|_|)
  let (|ClientTupleGet|_|)  = (|StdExpr|_|) >>? Raw.(|TupleGet|_|)
  
  let (|ClientEncodedWhileLoop|_|) = (|StdExpr|_|) >>? Raw.(|EncodedWhileLoop|_|)
  
  // For loop declared either as "for" or as a client monad 
  let (|ClientEncodedForLoop|_|) e =
    match e with 
    | StdExpr(Raw.EncodedForLoop(a)) -> Some(a,None)
    | StdExpr(GenericMethodUse (clientBind) (tya, [asyncInst; Raw.GenericTopDefnApp (<@@ client_for @@>) (_, args); cont])) -> 
        match args, cont with 
        | [Tuple(_, [f; t]); body], Lambda(_,cbody) ->
            Some((f,t,body),(Some cbody))
        | _ -> failwith "Invalid 'client_for' use!"
    | _ -> None
  
  let (|ClientUnit|_|) x =
    if (x = unitExpr) then Some() else None
  let (|UnitExpr|_|) = (|StdExpr|_|) >>? (|ClientUnit|_|)


  // Records are tricky.. - we need to make sure that the type is really record and not a F# class (records are used for fields as well)
  
  let (|TypeRecord|_|) t =
    match (Type.GetInfo t) with
      | RecordType(r) -> Some(t, r)
      | _ -> None
      
  let (|TypeNotRecord|_|) t =
    match (Type.GetInfo t) with
      | RecordType(r) -> None
      | _ -> Some(t)
    
  let (|ClientRecdMake|_|) x =
    match x with 
      | StdExpr(Raw.Recd(TypeRecord(ty, r), exprs)) -> Some(ty, r, exprs)
      | _ -> None

  let (|ClientRecdGet|_|) x =
    match x with 
      | StdExpr(Raw.RecdGet(TypeRecord(r), s, expr)) -> Some(s, expr)
      | _ -> None

  let (|ClientRecdSet|_|) x =
    match x with 
      | StdExpr(Raw.RecdSet(TypeRecord(r), s, inst, expr)) -> Some(s, inst, expr)
      | _ -> None

  // Combinations with additional handling
  
  let (|ClientVariableInvoke|_|) x = 
    match x with 
    | StdExpr(Raw.Apps(Var(v), args)) when args <> [] -> Some(v,args)
    | _ -> None

  let rec getFuncArgTypes t = 
    match Type.GetInfo (t) with
      | FunctionType(f,t) -> f::(getFuncArgTypes t)
      | _ -> []

  let (|ClientLambdaApp|_|) x = 
    match x with 
    | StdExpr(Raw.Apps(Raw.Lambdas(vars, body) as l, args)) when args.Length>0 && args.Length=vars.Length -> 
        Some(l, args)
    | _ -> None
      
  let (|ClientCurrying|_|) x = 
    try
      match x with 
      | StdExpr(Raw.Apps(e, args)) when args <> [] -> 
          match getFuncArgTypes (e.Type) with
            | l when l.Length > args.Length -> 
                Some(e, l, args)
            | _ -> None
      | _ -> None
    with _ ->  // TODO: e.Type throws for generic - this truly sucks
      None
      
      
  let (|ClientLet|_|) x = 
    match x with 
    | StdExpr(Raw.Let(a,b)) -> Some([a], b)
    | StdExpr(Raw.LetRec(a,b)) -> Some(a,b) 
    | StdExpr(Raw.App(Raw.Lambda(v, body), binding)) -> Some([v, binding], body)
    | _ -> None;
    
  let (|ClientValueRef|_|) x =
    match x with 
    | StdExpr(Raw.Var v) -> Some v.Text 
    | ScriptValueRef(n) -> Some n
    | _ -> None;

  let (|ClientAttachHandler|_|) x = 
    match x with 
    | StdExpr(Fix.MethodCall(meth, args)) ->
        let attrs = meth.GetCustomAttributes((type AttachEventHandlerAttribute), false) in 
        if (attrs.Length = 0) then None else Some(args.Head, args.Tail)
    | _ -> None;

  let (|ClientPrimitive|_|) x =
    match x with
    | StdExpr(Bool b) -> Some(if (b) then "true" else "false")
    | StdExpr(Double v) -> Some(any_to_string v)
    | StdExpr(Int16 v) -> Some(any_to_string v)
    | StdExpr(Int32 v) -> Some(any_to_string v) 
    | StdExpr(Int64 v) -> Some(any_to_string v)
    | StdExpr(UInt16 v) -> Some(any_to_string v)
    | StdExpr(UInt32 v) -> Some(any_to_string v) 
    | StdExpr(UInt64 v) -> Some(any_to_string v)
    | StdExpr(Char v)   -> Some(any_to_string v)
    | StdExpr(String v) -> Some(any_to_string v)
    | _ -> None

  let (|ClientLambda|_|) x =
    match x with 
    | StdExpr(Lambda(v) as e) ->
        match e with Lambdas(vars, expr) -> Some (vars, MkStdExpr expr)
    | ScriptLambda(v) -> Some(v)
    | ExtExpr e -> None
    | _ -> None
  
  let (|ClientSeq|_|) x =
    match x with 
    | StdExpr(Raw.Seq(a,b)) -> Some([a; b] |> toExtExpr)
    | ScriptSeq a -> Some a
    | _ -> None

  // Monads

  let (|ClientServerCall|_|) x =
    match x with 
    | StdExpr (Raw.GenericTopDefnApp (serverExecExpr) (_, [svr])) ->
        match svr with
        | Fix.MethodCall(meth, args) ->
            if (isServerType meth.ReturnType) then 
              Some(meth, args) 
            else 
              failwith "Remote call must be call to method with 'RemoteCallState<'a>' type!"
        | _ -> None
    | _ -> None
    
  // Handles F# constructors (this is bad..)
  
  let (|ClientFieldInit|_|) x =
    match x with 
      | StdExpr(Raw.Recd (TypeNotRecord(t), exprs)) -> 
          // Seq contains call to parent constructor (which we ignore... too bad) and recd is field initialization..
          // Represents code in constructor (in the { .. } block) TODO: now this is UBERUGLY :)
          let zipped = 
            t.GetFields(Enum.combine [BindingFlags.DeclaredOnly; BindingFlags.NonPublic; 
                                      BindingFlags.Instance; BindingFlags.Public; ]) 
              |> Array.to_list |> List.zip exprs 
          let init = 
            zipped |> List.map ( fun (expr,fld) ->
              MkFieldSet ((MkValueRef "this"), fld.Name.Substring(1), MkStdExpr expr))
          Some(init)
      | _ -> None

  // Patterns for calls to [ClientSide] or [PartialClientSide] or [NativeClientClass] F# objects
  
  let (|ClientSideCode|_|) (mi:#MemberInfo) =
    let natclattrs = mi.DeclaringType.GetCustomAttributes((type NativeClientSideAttribute), false)    
    if (natclattrs.Length > 0) then Some(mi) else
      let clattrs = mi.DeclaringType.GetCustomAttributes((type ClientSideAttribute), false)
      if (clattrs.Length = 0) then None else
        if (clattrs.[0] :? MixedSideAttribute) then
          let attrs = mi.GetCustomAttributes((type WebBehaviorAttribute), false)
          let dattrs = mi.GetCustomAttributes((type DuplexFieldAttribute), false)
          if (dattrs.Length > 0) || (attrs.Length > 0 && (Enum.test (attrs.[0] :?> WebBehaviorAttribute).Behavior WebBehavior.ClientSide)) then
            Some(mi)
          else 
            match (box mi) with 
            | :? MethodInfo as mmi when isClientType mmi.ReturnType -> Some(mi)
            | :? PropertyInfo as pmi when isClientType pmi.PropertyType -> Some(mi)
            | _ -> None
        else 
          Some(mi)
    
  let (|ClientConstructorCall|_|) x = 
    match x with
    | StdExpr(Fix.CtorCall(ClientSideCode(meth),args)) -> Some(meth.DeclaringType, getClassName (meth.DeclaringType), detupleArgs args)
    | _ -> None    

  // calls to the base constructor 
  
  let rec clientBaseClass (t:Type) =
    if (t = null) then 
      None 
    else
      match t.GetCustomAttributes((type ClientSideAttribute), false).Length with
        | n when n>0 -> Some t;
        | _ -> clientBaseClass (t.BaseType);
 
  let (|ResolveClientCtor|_|) (mi:ConstructorInfo, tys:Type list) =
    match mi with
    | ClientSideCode(_) -> Some(mi)
    | _ when tys.Length <> 0 -> None
    | _ -> 
      match clientBaseClass mi.DeclaringType with
      | Some(t) -> 
          let cts = t.GetConstructors()
          if (cts.Length <> 1 || cts.[0].GetParameters().Length <> 0) then 
            None
          else
            Some(cts.[0])            
      | _ -> None
    
  let (|ClientBaseConstructorCall|_|) x = 
    match x with      
    | StdExpr(Raw.Seq(rest,Fix.CtorCall(ct, args))) ->
        let ats = args |> List.map (fun a -> a.Type)
        match (ct,ats) with 
          | ResolveClientCtor(ct) -> Some(Some(rest), (ct,args), None) 
          | _ -> None
    | StdExpr(Raw.Seq(Fix.CtorCall(ct, args),rest)) ->
        let ats = args |> List.map (fun a -> a.Type)
        match (ct,ats) with 
          | ResolveClientCtor(ct) -> Some(None, (ct,args), Some(rest)) 
          | _ -> None
    | _ -> None
  
  // properties, methods, etc...
    
  let (|ClientPropertySet|_|) x = 
    match x with
    | StdExpr(Fix.PropSet(ClientSideCode(prop), args)) -> 
        if (isPropStatic prop) then
          Some(prop.Name, None, args.Head)
        else
          Some (prop.Name, Some (args.Head), args.Tail.Head)
    | _ -> None;
  
  let (|ClientPropertyGet|_|) x =    
    match x with
    | StdExpr(Fix.PropGet(ClientSideCode(prop),args)) -> 
        if (isPropStatic prop) then
          Some (prop.Name, Some (MkValueRef prop.DeclaringType.Name), (removeUnitArgs args) |> toExtExpr)
        else
          match args with
          | self::other -> Some(prop.Name, Some (MkStdExpr self), (removeUnitArgs other) |> toExtExpr)
          | _ -> failwith "Calling non-static property without this!";
    | _ -> None;
        
  let (|ClientFieldSet|_|) x = 
    match x with
    | StdExpr(Raw.RecdSet(TypeNotRecord(t), n, inst, nval)) -> Some(n, Some (MkStdExpr inst), (MkStdExpr nval)); // not checked for [clientside]
    | ScriptFieldSet(inst, n, v) -> Some(n, Some inst, v)
    | _ -> None;

  let (|ClientFieldGet|_|) x = 
    match x with
    | StdExpr(Raw.RecdGet(TypeNotRecord(t), n, expr)) -> Some (n, MkStdExpr expr) // not checked for [clientside]
    | StdExpr(Raw.FieldGet(ClientSideCode(f), expr)) -> Some (f.Name, MkStdExpr expr)
    | _ -> None;      

  let (|ClientMethodCall|_|) x =   
    match x with
    | StdExpr(Fix.MethodCall(ClientSideCode(meth),args)) ->  // todo verify number of arguments?
        let (inst, args) = 
          match args with 
          | a::rest -> 
              if (meth.IsStatic) then 
                (Some (MkValueRef (getClassName meth.DeclaringType)), toExtExpr (a::rest))
              else
                (Some (MkStdExpr a), toExtExpr rest)
          | [] -> 
              if (meth.IsStatic) then 
                (Some (MkValueRef (getClassName meth.DeclaringType)), [])
              else
                (None, []) 
        let args = 
          match args with 
          | [StdExpr(Tuple(_, vals))] -> toExtExpr vals
          | _ -> args           
        Some(meth.Name, inst, args) 
        
    | StdExpr(Raw.App _ as e) ->  // TODO: this is not exactly correct, because it doesn't check if the class is client-side class
        match e with              // or if the indexer is mapped to something.. but it's not possible without knowing the type...
        | Apps(Raw.AnyTopDefnUse(td,_), args) ->
            let (sl, n) = td.Path
            if (n = "op_IndexedLookup") then
              Some("get_Item", Some(MkStdExpr(args.Head)), args.Tail |> toExtExpr)
            else 
              None
        | _ -> None
    | _ -> None
  
  // Patterns & utils for recognizing calls to .NET methods with mapping attribute
              
  let (|MemberMapping|_|) (mi:#MemberInfo) =
    let attrs = mi.GetCustomAttributes((type MappingAttribute), false)
    if (attrs.Length = 0) then None else 
      let mm = attrs.[0] :?> MappingAttribute
      Some(mm.Name, mm.Type, mm.Scope)
      
  let getTypeMapping (t:Type) =
    match (t :> MemberInfo) with 
    | MemberMapping(s, typ, scope) ->
        if (scope <> MappingScope.Global) then failwith "Can't map type to instance member!";
        match typ with 
        | MappingType.Method   -> [MkEmbeddedExpr(s + "(alert('...TODO...'))")]
        | MappingType.Field    -> [MkValueRef(s)]
        | MappingType.Property -> [MkValueRef("get_" + s + "(alert('...TODO...'))")]
        | MappingType.Object   -> [MkEmbeddedExpr("new " + s + "(alert('...TODO...'))")]
        | MappingType.Inline   -> [MkValueRef(s)]
        | _ -> assert(false);
    | _ -> []

  type AccessType = | Get = 0 | Set = 1 | Call = 2
  let (|MappedMemberInvoke|_|) x =
    match x with
    | StdExpr(Fix.CtorCall(MemberMapping(str, typ, scope) as meth,args)) ->
        Some((meth :> MemberInfo), true, str, typ, scope, AccessType.Call, args);
    | StdExpr(Fix.PropGet(MemberMapping(str, typ, scope) as prop, args)) ->   
        Some((prop :> MemberInfo), (isPropStatic prop), str, typ, scope, AccessType.Get, args);
    | StdExpr(Fix.PropSet(MemberMapping(str, typ, scope) as prop, args)) ->   
        Some((prop :> MemberInfo), (isPropStatic prop), str, typ, scope, AccessType.Set, args);
    | StdExpr(Fix.MethodCall(MemberMapping(str, typ, scope) as meth, args)) ->   
        Some((meth :> MemberInfo), meth.IsStatic, str, typ, scope, AccessType.Call, args);
    | _ -> None

  type ClientInvokeType = | Expression = 0 | Statement = 1                
  let (|ClientNativeInvoke|_|) invtype x =
    match x with
    | MappedMemberInvoke(mi, isstatic, str, typ, scope, acc, args) ->
        let args = toExtExpr (detupleArgs args)
        let args = if (isstatic) then (getTypeMapping mi.DeclaringType)@args else args
        
        // instance & arguments
        let (inst, args) = 
          if (scope = MappingScope.Global) then
            None, args
          else
            match args with 
            | a::rest -> (Some a), (detupleArgsEx rest)
            | _ -> failwith "Can't map to instance member without any arguments!";
        let args = removeUnitArgsEx args;
        
        // JS code to call
        let ((call,inln),ty) = 
          match typ with 
          | MappingType.Method   -> ((str,false), ClientInvokeType.Expression)
          | MappingType.Field when acc = AccessType.Get && args = []  -> ((str, true), ClientInvokeType.Expression)
          | MappingType.Field when acc = AccessType.Set && args.Length = 1  -> ((str + " = ", true), ClientInvokeType.Statement)
          | MappingType.Field -> failwith ("Mapping to a field with wrong number of parameters or mapping method to field which isn't allowed! " + (expr_to_string x)) 
          | MappingType.Property -> 
              match acc with 
              | AccessType.Get ->   (("get_" + str,false), ClientInvokeType.Expression)
              | AccessType.Set ->   (("set_" + str,false), ClientInvokeType.Expression)
              | _ -> failwith "Can't map method to a property!"
          | MappingType.Object   -> (("new " + str,false), ClientInvokeType.Expression)
          | MappingType.Inline   -> ((str,true), ClientInvokeType.Expression)
          | _ -> assert(false);
          
        if (ty = invtype) then          
          Some (call, inst, args, inln)
        else 
          None
    | _ -> None;

  
  // Client async monads
  
  let (|ClientAsyncLet|_|) e = 
    match e with 
    | StdExpr(GenericMethodUse (asyncBind) (tya, asyncInst::args)) ->
        match args with 
        | [v; cont] ->
            let t = v.Type
            if (t.IsGenericType && t.GetGenericTypeDefinition() <> clientAsyncMonadTy) then 
              Some(tya, v, cont)
            else
              None
        | _ -> failwith "Incorrect 'clientAsyncLet'!"            
    | StdExpr(GenericMethodUse (asyncLet) (tya, [asyncInst; v; cont])) ->
        Some(tya, v, cont)
    | _ -> None
    
  let (|ClientAsyncBind|_|) e =
    match e with
    | ClientAsyncLet _ -> None
    | StdExpr(GenericMethodUse (asyncBind) (tya, asyncInst::args)) ->
        Some(tya, args)
    | _ -> None

  // Other client monads (translated to ordinary non-monadic JS code)
  
  let (|ClientMonadResult|_|) e =
    match e with 
    | StdExpr(GenericMethodUse (duplexRes) (tya, [asyncInst; value]))
    | StdExpr(GenericMethodUse (clientRes) (tya, [asyncInst; value])) -> Some(tya, value)
    | _ -> None

  let (|ClientMonadZero|_|) e =
    match e with 
    | StdExpr(GenericMethodUse (duplexZero) (tya, [asyncInst]))
    | StdExpr(GenericMethodUse (clientZero) (tya, [asyncInst])) -> Some(tya)
    | _ -> None

  let (|AnyMonadicDoOrLet|_|) e =
    [duplexBind; clientBind] 
      |> List.first (fun tdd ->
          match e with 
          | StdExpr(GenericMethodUse (tdd) (tya, [asyncInst; v; cont])) -> Some(tya, v, cont)
          | _ -> None )
        
  let (|ClientMonadBindLet|_|) e =
    match e with 
    | AnyMonadicDoOrLet(tya, v, cont) ->
        match v,cont with 
          | doops, Lambda(va, body) -> Some(tya, (va, doops), body)
          | _ -> failwith "Unexpected \"let\" client monad use!"
    | _ -> None          

  let (|ClientMonadDelay|_|) e =
    match e with 
    | StdExpr(GenericMethodUse (clientDelay) (tya, [asyncInst; func])) 
    | StdExpr(GenericMethodUse (duplexDelay) (tya, [asyncInst; func])) 
    | StdExpr(GenericMethodUse (asyncDelay)  (tya, [asyncInst; func])) ->
        match func with 
        | Lambda(v,b) -> Some(b)
        | _ -> failwith "Incorrect Delay binder use!"
    | _ -> None          

  let (|ClientMonadBindLetVal|_|) e =
    match e with 
    | StdExpr(GenericMethodUse (duplexLet) (tya, [asyncInst; v; cont]))
    | StdExpr(GenericMethodUse (clientLet) (tya, [asyncInst; v; cont])) ->
        match v,cont with 
          | doops, Lambda(va, body) -> Some(tya, (va, doops), body)
          | _ -> failwith "Unexpected \"letVal\" client monad use!"
    | _ -> None          

(*-----------------------------------------------------------------------------------------------*)
module Generator = 
(*-----------------------------------------------------------------------------------------------*)
// Combinators for generating text

  type Context = 
    { Writer:IndentedTextWriter; CurrentClass:ClassInfo option; 
      PrettyVariables:Set<string>; ThisVar:string option; }
  
  let createContext(wr:#IO.TextWriter) =
    { Writer = new IndentedTextWriter(wr); CurrentClass = None; PrettyVariables = Set.empty; ThisVar = None; }

  let setCurrentClass (c:ClassInfo option) ctx = 
    { ctx with CurrentClass=c; }
  
  let delayed f ctx = 
    (f ctx) ctx
    
  let (+>) (ctx:Context) (foo:Context -> Context) = 
    foo ctx;
    
  let (++) (ctx:Context) (str:String) =
    ctx.Writer.WriteLine(str);
    ctx;
    
  let (--) (ctx:Context) (str:String) =
    ctx.Writer.Write(str);
    ctx;

  let id a = a    
  let str o ctx =
    ctx -- (any_to_string o);

(*-----------------------------------------------------------------------------------------------*)
// Basic code-generation functions

  let declarePrettyVariables thisvar (vars:string list) f ctx =
    let (bpv,btv) = ctx.PrettyVariables, ctx.ThisVar
    let nset = Set.union (Set.of_list vars) bpv
    let nthis = match thisvar with Some v -> Some v | _ -> btv
    let ctx = { ctx with PrettyVariables = nset; ThisVar = nthis; } 
    let ctx = (f ctx)
    { ctx with PrettyVariables = bpv; ThisVar = btv;} 
        
  let writeVariableName (name:String) ctx =
    let s = 
      match ctx.PrettyVariables, ctx.ThisVar with 
        | _ when name.StartsWith("this#") -> "this" // TODO: ugly!!! // otherwise it doesn't handle "ScriptLambda" correctly
        | _, Some(v) when v = name -> "this"
        | (pv, _) when (Set.subset (Set.of_list [name]) pv) ->
            let i = name.IndexOfAny([|'@'; '#'; '.'|])
            (if (i = -1) then name else name.Substring(0, i))
        | _ ->
            ["@"; "#"; "."; "-"] |> (name |> List.fold_left(fun n s -> n.Replace(s,"$")))      
    ctx.Writer.Write(s);
    ctx

  let writeVariableNameWriter (name:String) (wr:IndentedTextWriter) =
    writeVariableName name (createContext(wr)) |> ignore

  let incIndent ctx =
    ctx.Writer.Indent <- ctx.Writer.Indent + 1
    ctx 

  let decIndent ctx =
    ctx.Writer.Indent <- ctx.Writer.Indent - 1
    ctx 
    
  let indented f ctx = 
    ctx.Writer.Indent <- ctx.Writer.Indent + 1
    let ctx = f ctx
    ctx.Writer.Indent <- ctx.Writer.Indent - 1
    ctx
    
  type ConstructType = 
    | Statement
    | LastStatement
    | Expression
    | Unit

(*-----------------------------------------------------------------------------------------------*)

  let getStdExpr e = 
    match e with 
    | StdExpr e -> e 
    | _ -> failwith ("not supported "+(any_to_string e));
    
  /// Call function for every member of specified family
  let membersFilter (fnc:ClassMember -> 'a option) =
    List.filter ( fun m -> match (fnc m) with | Some v -> true; | None -> false; )


  /// Call function for every member of specified family
  let membersMapFilter (fnc:ClassMember -> 'a option) members =
    members |> Utils.map_filter ( fun m -> 
      match (fnc m) with | Some v -> Some v; | None -> None; )
    
(*-----------------------------------------------------------------------------------------------*)
// GenerateStatement & GenerateExpression functions

  open ClientExpressions
  
  let rec generateFunctionAppArgs ty args ctx =
    let genExpr e = writeExpr Expression (MkStdExpr e)
    let args = (detupleArgs args)
    ctx -- "("
    +> (args |> Utils.fold_sep genExpr (fun ctx -> (ctx -- ", "))) -- ")"

  /// Generates JavaScript code for a statement stored in Extended expression 
  /// and calls a 'continuation' function if fails to match
  and generateStatement (lst:ConstructType) (e:ExtendedExpr) continuation ctx =
    let genExpr e = writeExpr Expression (MkStdExpr e)
    let genExprEx = writeExpr Expression 
    let genStmt e = writeExpr Statement (MkStdExpr e)
    let genStmtEx = writeExpr Statement
    let genLst e  = writeExpr lst (MkStdExpr e)
    let genRet e  = writeExpr LastStatement (MkStdExpr e)
    let genLstEx  = writeExpr lst
    let genLstIndented e  = indented (writeExpr lst (MkStdExpr e))
    let genStmtIndented e = indented (writeExpr Statement (MkStdExpr e))
    
    match e with 
      | ClientCond (cond,tr,fl) when fl.Type = (type unit) -> // match all statments here (if type is Unit)
          (Lazy.force ctx)
          -- "if ("
          +> genExpr cond
          ++ ") {"
          +> genStmtIndented tr
          +> if (fl <> unitExpr) then 
               (fun ctx -> ctx ++ "} else {" +> genStmtIndented fl)
             else 
               id
          ++ "}"
                
      |  ClientLet (bindList, rest) ->
          (Lazy.force ctx)
          //-- "(function() { var " // TODO: scope?
          +> Utils.fold_st (fun (var:ExprVar, bind) ctx -> 
              ctx -- "var "
              +> writeVariableName var.Name.Text 
              -- " = "
              +> genExpr bind
              ++ ";" ) bindList
          +> genLst rest 
          //++ "}).apply(this,[]);"
          
      | ClientEncodedWhileLoop (cond,body) ->
          (Lazy.force ctx)
          -- "while ("
          +> genExpr cond
          ++ ") {"
          +> genStmtIndented body // todo: !! decode lambda??
          ++ "}"
          
      | ClientEncodedForLoop ((nfrom,nto,body), cont) ->
          let (var, innerBody) = match body with | Raw.Lambda v -> v; | _ -> failwith "Invalid for loop!"; in
          (Lazy.force ctx)
          -- "for(var $f_" +> writeVariableName (var.Name.Text) -- "=" +> genExpr nfrom -- "; $f_"
          +> writeVariableName (var.Name.Text) -- "<=" +> genExpr nto -- "; $f_"
          +> writeVariableName (var.Name.Text) ++ "++) { Function.createDelegate(this, function() {"
          +> indented 
               (fun ctx ->
                ctx -- "var " +> writeVariableName (var.Name.Text) -- " = $f_" +> writeVariableName (var.Name.Text) ++ ";"
                +> genStmt innerBody)
          ++ "})(); }"
          +> match cont with 
              | Some(c) -> genLst c
              | _ -> id
          
      // this needs to go before calls to ClientSide classes (because some ClientSide classes has [Mapping] members!)
      | ClientNativeInvoke ClientInvokeType.Statement (code, inst, args, inln) ->
          let cc = code + ""
          match inst with | Some(expr) -> (Lazy.force ctx) +> genExprEx expr -- "." | _ -> (Lazy.force ctx)
          -- code
          +> if (args = [] && inln) then id else
               (fun ctx -> ctx -- "(" +> (args |> Utils.fold_sep genExprEx (fun ctx -> ctx -- ", ")) ++ ");")
          
      | ClientPropertySet (name,inst,arg) -> 
          match inst with | Some(expr) -> (Lazy.force ctx) +> genExpr expr -- "." | _ -> (Lazy.force ctx)
          -- "set_" -- name
          -- "("
          +> genExpr arg
          ++ ");"

      | ClientFieldSet (name,inst,arg) -> 
          match inst with | Some(expr) -> (Lazy.force ctx) +> genExprEx expr -- "." | _ -> (Lazy.force ctx)
          -- name
          -- " = "
          +> genExprEx arg
          ++ ";"
          
      | ClientAttachHandler (inst, args) ->
          (Lazy.force ctx) +> genExpr inst 
          -- ".add("
          +> (args |> Utils.fold_sep genExpr (fun ctx -> ctx -- ", ")) ++ ");"

      | ClientFieldInit (initfl) ->
          (Lazy.force ctx) +> Utils.fold_st (fun a ctx -> ctx +> genStmtEx a) initfl

      | ClientRecdSet(n, inst, value) ->
          (Lazy.force ctx) +> genExpr inst -- "." -- n -- " = " +> genExpr value -- ";"


      // TODO: constructor hacks
      | ClientBaseConstructorCall(bef, (ct,args), after) ->
          (Lazy.force ctx)
          +> (match bef with Some(s) -> genStmt s | _ -> id)
          ++ "// ctor"
          +> delayed (fun x -> match x.CurrentClass with Some c -> (fun ctx -> ctx -- c.Name) | _-> assert(false)) 
          -- ".callBaseMethod(this, \"ctor\", [" 
          +> (args |> Utils.fold_sep genExpr (fun ctx -> ctx -- ", ")) 
          ++ "]);"          
          +> (match after with Some(s) -> genStmt s | _ -> id)
          
      | ClientSeq (l) ->
          let lf = l |> List.mapi ( fun i e -> if (i = l.Length - 1) then (genLstEx, e) else (genStmtEx, e) )
          (Lazy.force ctx) +> Utils.fold_st ( fun (f, v) x -> x +> f v ) lf
          
      // client monad
      | StdExpr(Raw.GenericTopDefnApp (trackedAssignmentExpr) (tya, [Quote(expr); liftObj; liftVal])) ->
          match liftObj, liftVal with 
            | Raw.GenericTopDefnApp (<@@ Typed.lift @@>) (_, [exprObj]), Raw.GenericTopDefnApp (<@@ Typed.lift @@>) (_, [exprVal]) ->
                let e,_ = fillHoles [exprObj; exprVal] expr 
                (Lazy.force ctx) +> genLst e
            | _ -> failwith "Incorrect <@! .. !@> code!"
          
      | StdExpr(Raw.GenericTopDefnApp (makeDuplexExpr) (tya, [_; clientCode])) ->
          match (MkStdExpr clientCode) with
          | ClientMonadDelay(_) 
          | ClientMonadResult(_, _) 
          | ClientMonadBindLet(_, _, _)
          | ClientMonadZero(_)
          | ClientMonadBindLetVal(_, _, _) -> 
              (Lazy.force ctx) +> genLst clientCode
          | _ -> failwithf "Incorrect makeDuplex - only monadic expression is allowed "
                           "as a client code (no top-level definitions or so..). Got:\n%A" clientCode
                
      | ClientMonadDelay(body) ->                 
          (Lazy.force ctx) +> genLst body // Delay - just generate body directly
      
      | ClientMonadZero(tya) ->
          (Lazy.force ctx)
                      
      | ClientMonadResult(tya, value) ->
          match value with
          | u when u = unitExpr -> (Lazy.force ctx)
          | value -> (Lazy.force ctx) +> genLst value
          //| _ -> failwith "Unexpected result structure in monad!"

      | ClientMonadBindLet(tya, (va, doops), body) ->
          if (va.Type = (type unit)) then 
            (Lazy.force ctx) +> genLst (MkSeq(doops, body))
          else
            (Lazy.force ctx) +> genLst (Unchecked.MkLet((va, doops), body)) // let (va:int) = ( ... : Client<int>) in ... // needs to be unchecked

      | ClientMonadBindLetVal(tya, (va, doops), body) ->
          if (va.Type = (type unit)) then 
            (Lazy.force ctx) +> genLst (MkSeq(doops, body))
          else
            (Lazy.force ctx) +> genLst (Unchecked.MkLet((va, doops), body)) // TODO: bug - using value type as a record in one situation causes an issue here
            
      | StdExpr(Raw.GenericTopDefnUse (clientUnitExpr) (tya)) ->
          (Lazy.force ctx)
            
      | _ -> continuation e ctx


  /// Generates JavaScript code for an expression stored in Extended expression 
  /// and calls a 'continuation' function if fails to match
  and generateExpression (e:ExtendedExpr) continuation ctx =
    let genExpr e = writeExpr Expression (MkStdExpr e)
    let genExprEx e = writeExpr Expression e
    let genStmt e = writeExpr Statement (MkStdExpr e)

    match e with 
      | ScriptEmbedded (str) ->
          (Lazy.force ctx) -- str
          
      | StdExpr(GetAddr e) ->
          (Lazy.force ctx) +> genExpr e
          
      | StdExpr(LazyAnd(l,r)) ->
          (Lazy.force ctx) -- "(("  +> genExpr l -- ") && (" +> genExpr r -- "))"

      | StdExpr(LazyOr(l,r)) ->
          (Lazy.force ctx) -- "(("  +> genExpr l -- ") || (" +> genExpr r -- "))"
          
      | ClientCond (cond,tr,fl) -> // expression - statements handled earlier
          (Lazy.force ctx)
          -- "("
          +> genExpr cond
          -- ")?(Function.createDelegate(this, "
          +> indented 
            (fun ctx -> 
              ctx +> generateFunctionCode "" (MkStdExpr (MkLambda(ExprVar.Fresh("ni", (type unit)), tr))) true 
              -- ")()):(Function.createDelegate(this, "
              +> generateFunctionCode "" (MkStdExpr (MkLambda(ExprVar.Fresh("ni", (type unit)), fl))) true 
              -- ")())")
            
      | ClientValueRef (name) ->
          (Lazy.force ctx) +> writeVariableName name 

      | ClientNativeInvoke ClientInvokeType.Expression (code, inst, args, inln) ->
          let cc = code + ""
          match inst with | Some(expr) -> (Lazy.force ctx) +> genExprEx expr -- "." | _ -> (Lazy.force ctx)
          -- code
          +> if (args = [] && inln) then id else
               (fun ctx -> ctx -- "(" +> (args |> Utils.fold_sep genExprEx (fun ctx -> ctx -- ", ")) -- ")")
          
      | ClientMethodCall (name,inst,args) -> 
          match inst with | Some(expr) -> (Lazy.force ctx) +> genExprEx expr -- "." | _ -> (Lazy.force ctx)
          -- name
          -- "(" +> (args |> Utils.fold_sep genExprEx (fun ctx -> ctx -- ", ")) -- ")"

      | ClientConstructorCall (ty, name, args) ->
          let writeTya ctx = 
            if (ty.IsGenericType) then 
              let expArgs = ty.GetGenericArguments() |> Array.map(fun arg -> 
                MkString(Utils.encode arg.AssemblyQualifiedName)) |> Array.to_list
              ctx +> genExpr (MkNewArray((type string), expArgs))
            else
              ctx
          
          (Lazy.force ctx)
          -- "CreateObject(new " -- name  -- "(" +> writeTya -- "), ["
          +> Utils.fold_sep genExpr (fun ctx -> ctx -- ", ") args
          -- "])"
      
      | ClientPropertyGet (name,inst,args) -> 
          if (args <> []) then failwith "Inexers not supported!"
          match inst with | Some(expr) -> (Lazy.force ctx) +> genExprEx expr -- "." | _ -> (Lazy.force ctx)
          -- "get_" -- name -- "()"

      | ClientFieldGet (fi, inst) ->
          (Lazy.force ctx) +> genExprEx inst -- "." -- fi
          
      | ClientEquality (e1,e2) -> 
          (Lazy.force ctx) +> genExpr e1 -- "==" +> genExpr e2
          
      | ClientCoerce (_,e) -> 
          (Lazy.force ctx) +> genExpr e
          
      | ClientPrimitive (str) -> 
          (Lazy.force ctx) -- str
      
      | ClientNewArray (ty, args) ->
          (Lazy.force ctx) 
          -- "CreateArray([" +> (args |> Utils.fold_sep genExpr (fun ctx -> ctx -- ", ")) 
          -- "], \"" -- (Utils.encode (ty.MakeArrayType().AssemblyQualifiedName)) -- "\" "
          -- ")"
          
      | ClientUnionMake(ty, name, args) ->
          (Lazy.force ctx)
          -- "CreateObject(new DiscriminatedUnion(), ['" -- name -- "', ["
          +> (args |> Utils.fold_sep genExpr (fun ctx -> ctx -- ", ")) 
          -- "], \"" -- (Utils.encode ty.AssemblyQualifiedName) -- "\" "
          -- "])"

      | ClientUnionGet(ty, name, idx, inst) ->
          (Lazy.force ctx) +> genExpr inst
          -- ".Get('" -- name -- "', " +> str idx -- ")"

      | ClientUnionTest(ty, name, inst) ->
          if (ty.IsGenericType && ty.GetGenericTypeDefinition() = optionTy) then
            if (name = "None") then // F# compiles None as null, so we have to handle it here..
              (Lazy.force ctx) 
              -- "((" +> genExpr inst -- " == null) || (" 
              +> genExpr inst -- ".IsTag('None')))"
            else    
              (Lazy.force ctx) 
              -- "((" +> genExpr inst -- " != null) && (" 
              +> genExpr inst -- ".IsTag('Some')))"
          else
            (Lazy.force ctx) +> genExpr inst
            -- ".IsTag('" -- name -- "')"
          
      | ClientTupleMake(ty, args) ->
          (Lazy.force ctx) 
          -- "CreateObject(new Tuple(), [["
          +> (args |> Utils.fold_sep genExpr (fun ctx -> ctx -- ", "))
          -- "], \"" -- (Utils.encode ty.AssemblyQualifiedName) -- "\" "
          -- "])"

      | ClientTupleGet(ty, idx, inst) ->
          (Lazy.force ctx) +> genExpr inst
          -- ".Get(" +> str idx -- ")"                      
      
      | ClientRecdMake(ty, flds, init) ->
          let l = List.zip flds init 
          (Lazy.force ctx)
          -- "CreateRecord({ "
          +> Utils.fold_sep (fun ((n,t),exp) ctx ->
               ctx
               -- "\"" -- n -- "\": "
               +> genExpr exp ) (fun ctx -> ctx -- ", ") l
          -- "}, \"" -- (Utils.encode ty.AssemblyQualifiedName) -- "\")"

      | ClientRecdGet(n, inst) ->
          (Lazy.force ctx) +> genExpr inst -- "." -- n

      | ClientLambdaApp(lambda, args) ->
          (Lazy.force ctx)
          +> genExpr lambda
          +> generateFunctionAppArgs lambda.Type args
      
      | ClientCurrying(inst, atys, abinds) ->
          let vars = List.init (atys.Length - abinds.Length) (fun i -> 
                ExprVar.Fresh(sprintf "tmp%d" i, List.nth atys (i + abinds.Length)) )
          let varrefs = vars |> List.map (fun v -> MkVar v.Name)
          (Lazy.force ctx) +> genExpr (MkLambdas(vars, MkApps(inst, abinds@varrefs)))
      
      | ClientVariableInvoke(var, args) -> // TODO: may be currying
          (Lazy.force ctx)
          +> writeVariableName var.Text 
          +> generateFunctionAppArgs var.Type args
          
          
      // [MONADS] - these translate calls from F# monads to JS monads and therefore it has to use 
      //            Unchecked.Mk functions - this is bad, we could write our own monad, .. maybe, don't know ..
      
      | ClientServerCall(meth, args) ->        
          let toinvoke = urlEncode(meth.DeclaringType.AssemblyQualifiedName+"|"+meth.Name)
          let e = (<@@ new ClientLibrary.LibXmlHttpRequest("POST", "?scriptcmd=http-call&invoke=" + _); @@> (MkString toinvoke))
          let args = (detupleArgs args) |> List.map (fun e -> MkCoerce((type obj), e) )
          let inst,cargs = 
            if (meth.IsStatic) then
              <@@ None @@>, MkNewArray((type obj), args) 
            else
              match args with 
              | inst::rest -> <@@ Some(_ :> obj) @@> (inst), MkNewArray((type obj), rest) 
              | _ -> failwith "Missing instance!"              
          
          let args = ([e.Type], [MkTuple(mkTupTy3 e.Type (type obj option) (type obj[]), [e; inst; cargs])])
          (Lazy.force ctx) +> 
            genExpr (MkGenericMethodUse jsAsyncHttpCall args)

      | StdExpr(GenericMethodUse (asyncRes) (tya, asyncInst::args)) ->
          (Lazy.force ctx) 
          +> genExpr (MkGenericMethodUse jsAsyncRes (tya, args))

      | StdExpr(GenericMethodUse (asyncZero) (tya, asyncInst::args)) ->
          (Lazy.force ctx) 
          +> genExpr (MkGenericMethodUse jsAsyncZero (tya, args))

      | ClientAsyncBind(tya, args) ->
          (Lazy.force ctx) 
          +> genExpr (MkGenericMethodUse jsasyncBind (tya, args))
      
      | ClientAsyncLet(tya, v, cont) ->
          (Lazy.force ctx) 
          +> genExpr (MkGenericMethodUse jsasyncLet (tya, [v; cont]))
            
      // [/MONADS] 
            
      | StdExpr(Raw.Lambda(_) as e) ->
          let (args, body) = (|Lambdas|) e
          //let dbg = any_to_string body
          let returns = try (body.Type <> (type unit)) with _ -> true // TODO: workaround for possible crashes caused by wrong quotations!
          (Lazy.force ctx)
          -- "Function.createDelegate(this, "
          +> indented (generateFunctionCode "" (MkStdExpr e) returns)
          -- ")";

      // explicit conversions are done at the end, mainly because they handle (.[]<-) and
      // some other patterns may catch this and translate it directly to get_Item / set_Item
      
      | ClientExplicitlyMapped res // explicitly mapped topdef F# functions (failwith, clientRun, ..?) or type use (cls.Method()...)
      | ClientExternalTypeUse res ->
          (Lazy.force ctx) +> genExpr res

      //| StdExpr(Raw.AnyTopDefnUse (def,_)) ->  // ???
      //    let (path, name) = def.Path
      //    failwith name //id -- name
          
      | StdExpr(Raw.App _ as e) -> 
          let (op,args) = (|Apps|) e
          
          // Basic operators
          let argsArr = args |> List.to_array in
          let (opPath, op) = 
            match op with 
            | Raw.AnyTopDefnUse (a,_) -> a.Path 
            | _ -> ([""], any_to_string e) //let str = any_to_string e in failwith ("Function is not a top level definition. "^str) in
            
          // Basic binary operators
          let bop = 
            match op with
            | "op_Addition"    -> Some "+"
            | "op_Modulus"     -> Some "%"
            | "op_Division"    -> Some "/"
            | "op_Subtraction" -> Some "-"
            | "op_Multiply"    -> Some "*"
            | "op_Concatenate" -> Some "+"
            | "op_Equals"      -> Some "=="
            | "op_ColonEquals" -> Some "=" // TODO!! to neni ale expression ne??
            | "op_LessThan"    -> Some "<"
            | "op_GreaterThan" -> Some ">"
            | "op_LessThanOrEqual"    -> Some "<="
            | "op_GreaterThanOrEqual" -> Some ">="
            | "op_LessGreater" -> Some "!="
            | _ -> None in
            
          match bop with
            | Some opName ->
                (Lazy.force ctx)
                -- "("
                +> genExpr argsArr.[0]
                -- opName
                +> genExpr argsArr.[1]
                -- ")"
            | _ ->  
              // Something else
              match op with 
              | "ref"                         -> (Lazy.force ctx) +> genExpr argsArr.[0];
              | "op_UnaryNegation"            -> (Lazy.force ctx) -- "(-" +> genExpr argsArr.[0] -- ")";
              | "op_Dereference"              -> (Lazy.force ctx) +> genExpr argsArr.[0];
              | "op_DotLParenRParenLessMinus" -> (Lazy.force ctx) +> genExpr argsArr.[0] 
                                                 -- "[" +> genExpr argsArr.[1] -- "] = " 
                                                 +> genExpr argsArr.[2]
              | "op_DotLParenRParen"          -> (Lazy.force ctx) +> genExpr argsArr.[0] -- "[" 
                                                 +> genExpr argsArr.[1] -- "]"
              //| "length" -> 
              //    genExpr (MkExt  ExtPropertyGet (("length", Some argsArr.(0), [])) )
              //    
              //| "create" -> 
              //    writeExpression (MkExtMethodCall (("createArray", None, args)) )
              | _ -> 
                  let opFull = List.fold_left (fun st el -> (st^"."^el) ) "" (opPath@[op]) in
                  (Lazy.force ctx) -- "alert('JS Translation: Not supported operation - " -- opFull -- " - used in:\n" -- (any_to_string e) --"\n ')" 
                  
      | _ -> continuation e ctx

(*-----------------------------------------------------------------------------------------------*)
                        
  and writeExpr (expectedConstruct:ConstructType) (x:ExtendedExpr) ctx =  
    let lastStm = if (expectedConstruct = LastStatement) then LastStatement else Statement in
    // statements?
    // let strdebug = expr_to_string x
    let generatedConstruct = ref Statement in      
    
    // If we're expecting expression, we need to build lazy context that will wrap
    // statement when we get a statement, otherwise just use real statement
    let stmtCtx, stmExpected = 
      if (expectedConstruct = Expression) then
        Lazy.Create(fun () -> 
          ctx ++ "(Function.createDelegate(this, function() {"
          +> incIndent +> incIndent), LastStatement
      else 
        (lazy(ctx), expectedConstruct)
      
    // Run the generator
    let ctx = stmtCtx |> generateStatement stmExpected x (fun e _ ->    
      let ctx = if (expectedConstruct = LastStatement) then ctx -- "return " else ctx
      match x with
        | UnitExpr -> generatedConstruct := Unit; ctx
        | _ -> 
            generatedConstruct := Expression;
            lazy(ctx) |> generateExpression x (fun e _ -> 
              let strdebug = expr_to_string x
              match e with 
              | StdExpr(Fix.CtorCall(_)) -> ctx -- "/* Ignoring call to base class? */"
              | _ -> ctx -- "alert('whooops - " -- (strdebug) -- "')" ) );

    // statement vs. expression handling                        
    if ((!generatedConstruct = Statement) && (expectedConstruct = Expression)) then
      // end of the wrapping code started by lazy statement context
      ctx +> decIndent -- "})())" +> decIndent
    elif ((!generatedConstruct = Expression) && (expectedConstruct <> Expression)) then
      // end of expression if we're expecting statement
      ctx ++ ";"
    else
      ctx;

(*-----------------------------------------------------------------------------------------------*)
// Utilities for working with encoded expressions 

  and removeUnitParameters (vars:ExprVar list) = 
    match vars with
    | v::rest -> if (v.Type = (type Unit)) then rest else v::rest;
    | [] -> []

  and removeLast l = 
    match l with
    | v::rest -> if (rest = []) then [] else v::(removeLast rest);
    | [] -> []    
    
  and removeThisParam (vars:ExprVar list) =  
    match vars with
    | v::rest when rest.Length <= 1 && (v.Text.StartsWith("this#") || v.Text.StartsWith("this$")) -> 
        Some(v.Text), rest  // TODO!!!
    | _ -> None, vars
        
  /// Util - lambdas representing method take all parameters as tuple - this method 
  /// deconstructs the tuple into ordinary parameters
  and detupleLambda (vars, body) =
    let rec detuple (vars, body) remTup = 
      match body with
      | Let ((v, init), expr) ->
          (match init with 
            | TupleGet (t,n,e) -> 
                let nvars = if (remTup) then  (removeLast vars) else vars in
                ( detuple (nvars@[v], expr) false ) 
            | _ -> (vars, body))
      | _ -> (vars, body) in
      
    match body with 
    | StdExpr st -> 
        let tr, nth = removeThisParam vars
        let (nv,nb) = detuple (nth, st) true in (tr, nv, MkStdExpr(nb))
    | _ -> (None, vars, body);
    
(*-----------------------------------------------------------------------------------------------*)

  and generateExprCode (x:ExtendedExpr) (returns:bool) =            
    writeExpr (if returns then LastStatement else Statement) x
  
  and prepareMemberLambda (vars, body) =
    let (tr, vars, body) = detupleLambda (vars, body)
    (tr, vars |> removeUnitParameters, body)
    
  // Used for generating JS functions and JS classes 
  // (because JS classes are in fact functions)              
  and generateFunctionCodeExt (preFunc:Context->Context) (name:string) (expr:ExtendedExpr) (returns:bool) ctx =
    match expr with
    | ClientLambda (l) -> 
        let (tvr, vars, body) = prepareMemberLambda l
        ctx
        //+> match expr with StdExpr e -> id ++ "/* DEBUG: " -- name ++ (any_to_string e) ++ "*/" | _ -> id
        +> declarePrettyVariables tvr (vars |> List.map (fun v -> v.Text)) (fun ctx -> 
              match name with 
                | "" -> ctx -- "function ("
                | s  -> ctx -- "function " -- s -- " ("
             +> (vars |> Utils.fold_sep ( fun var -> writeVariableName var.Name.Text; ) ( fun ctx -> ctx -- ", " ))
             ++ ") {"
             +> indented (fun ctx -> ctx +> preFunc +> generateExprCode body returns)
             -- "}")
    | _ -> 
        failwith "Handler must be lambda expression!"

(*---------------------------------------------------------------------------*)

  and writeMethods (cls:ClassInfo) format st sep =
    // and static methods...
    let nf = (if st then id else not) in
    cls.Members 
      |> membersMapFilter (|Method|_|)
      |> List.filter ( fun (_, opts, _, _, _) -> nf (Enum.test opts MemberOptions.Static) )
      |> Utils.fold_st (fun (_, opts,name,expr,ret) ctx ->
           let expr = 
            if (Enum.test opts MemberOptions.Abstract) then
              MkStdExpr(<@@ fun () -> failwith "abstract method!"; @@>)
            else expr  
           ctx +> sep
           -- String.Format(format, name, cls.Name)
           +> generateFunctionCode "" expr (ret <> None) )
    
  and writeProperties (cls:ClassInfo) format st sep =
    let nf = (if st then id else not) in
     cls.Members 
      |> membersMapFilter (|Property|_|)
      |> List.filter ( fun (_, opts, _, _, _, _) -> nf (Enum.test opts MemberOptions.Static) )
      |> Utils.fold_st (fun (_, opts,name,getter,setter,typ) ctx ->
          ctx +> sep
          +> match getter with
             | None -> id
             | Some gexpr -> (fun ctx -> 
                  ctx -- String.Format(format, "get_"^name, cls.Name)
                  +> generateFunctionCode "" gexpr true)
          +> if (getter <> None && setter <> None) then sep else id
          +> match setter with
             | None -> id
             | Some sexpr -> (fun ctx ->
                  ctx -- String.Format(format, "set_"^name, cls.Name)
                  +> generateFunctionCode "" sexpr false)  )
    
  and writeFields (cls:ClassInfo) format st sep = 
    let nf = (if st then id else not) in
    let fields = 
      cls.Members 
        |> membersMapFilter (|Field|_|)
        |> List.filter ( fun (_, opts, _, _, _) -> nf (Enum.test opts FieldOptions.Static) )
    fields   
      |> Utils.fold_sep (fun (_, opts, name,t,initExpr) ctx ->
          ctx 
          -- String.Format(format, name)
          +> match initExpr with 
             | Some e -> (fun ctx -> ctx -- " = " +> generateExprCode e false)
             | _ ->  (fun ctx -> ctx ++ " = null;") ) sep

  /// Generates code for specified function.
  /// Function is specified by its name and expression that contains lambda function. 
  /// Set returns to true if method/expression has some return value.
  and generateFunctionCode = (generateFunctionCodeExt id)

  /// Generates code for specified class.
  and generateClassCode (cls:ClassInfo) ctx =
    // constructor?
    let ctors = (cls.Members |> membersMapFilter (|Constructor|_|) )
    let ctor = 
      match ctors with 
      | [] -> (MkStdExpr <@@ fun () -> (); @@>) 
      | [(_,ct)] -> ct 
      | _ -> failwith "Multiple constructors are TODO"
    
    // class is function and its body is class constructor
    // first lines in function are fields declarations, last are other members
    let sep = (fun ctx -> ctx ++ ",")
    
    ctx
    +> setCurrentClass (Some cls)
    -- cls.Name ++ " = function (tyargs) { "
    +> indented (fun ctx ->
          ctx
          ++ "if (tyargs!=null && tyargs.length>0) this.__type_args__ = tyargs;"
          // initialize base & generate fields      
          // TODO: this should be done better... somehow (we need to store call to base class ctor somewhere, but this is 
          // difficult with client-side controls.. maybe adding [<ClientSide>] Constructor(el) : base(el) { .. } could work)      
          +> (match cls.BaseName with 
              | None -> id
              | Some n -> (fun ctx -> ctx -- cls.Name ++ ".initializeBase(this);"))
          +> writeFields cls "this.{0}" false id )
    ++ "};"
        
    // register class
    ++ ""
    -- cls.Name ++ ".prototype = {"
    +> indented (fun ctx -> 
         // TODO: Proper constructor generation & multiple constructors
         ctx -- "ctor : "
         +> generateFunctionCode "" ctor false 
         +> writeMethods cls "{0} : " false sep
         +> writeProperties cls "{0} : " false sep)
    ++ "" ++ "};"
    
    -- cls.Name -- ".registerClass('" -- cls.Name -- "'"
    +> match cls.BaseName with 
       | None -> id
       | Some n -> (fun ctx -> ctx -- ", " -- n)
    ++ ");"
    
    // and static methods, properties & fields...
    +> writeMethods cls "\n{1}.{0} = " true id 
    +> writeProperties cls "\n{1}.{0} = " true id
    +> writeFields cls "\n{1}.{0}" true id 
    ++ "" ++ ""
    +> setCurrentClass (None)